package com.hwtx.form.domain.service;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.google.common.collect.Sets;
import com.hwtx.form.annotation.Action;
import com.hwtx.form.annotation.FormType;
import com.hwtx.form.controller.FormController;
import com.hwtx.form.domain.FormNotExistException;
import com.hwtx.form.domain.def.FormDef;
import com.hwtx.form.domain.def.Menu;
import com.hwtx.form.domain.def.ResourceTreeOption;
import com.hwtx.form.domain.repo.RoleResRepo;
import com.hwtx.form.persistence.entity.SysFunctionResourceMapping;
import com.hwtx.form.persistence.mapper.SysFunctionResourceMappingMapper;
import com.hwtx.form.util.FormUtil;
import io.geekidea.boot.auth.cache.LoginCache;
import io.geekidea.boot.auth.util.LoginUtil;
import io.geekidea.boot.common.constant.SystemConstant;
import io.geekidea.boot.framework.exception.BusinessException;
import io.geekidea.boot.system.entity.SysMenu;
import io.geekidea.boot.system.enums.SysMenuType;
import io.geekidea.boot.system.mapper.SysMenuMapper;
import io.geekidea.boot.system.query.SysMenuQuery;
import io.geekidea.boot.system.vo.PermissionVo;
import io.geekidea.boot.system.vo.SysMenuTreeVo;
import io.geekidea.boot.util.ObjectValueUtil;
import io.swagger.v3.oas.annotations.Operation;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.compress.utils.Lists;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.stream.Streams;
import org.springframework.beans.BeanUtils;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;

import java.lang.reflect.Method;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;

import static com.hwtx.form.domain.FormConstants.*;
import static com.hwtx.form.util.FormUtil.getFormIdByMenuCode;

/**
 * 系统菜单 服务实现类
 */
@Slf4j
@Service
public class ResourceServiceImpl extends ServiceImpl<SysMenuMapper, SysMenu> implements ResourceService {

    private static final Cache<Long, Long> resourceWithPId = CacheBuilder.newBuilder().maximumSize(300).expireAfterAccess(1, TimeUnit.DAYS).recordStats().build();
    private final Set<String> LIST_URL = Sets.newHashSet(LIST_URL_COLLECTION);
    private final Set<String> uniqueUrls = Sets.newHashSet();
    int staticIndex = 100;
    @Resource
    private SysMenuMapper sysMenuMapper;
    @Resource
    private RoleResRepo roleResRepo;
    @Resource
    private FormService formService;
    @Resource
    private SysFunctionResourceMappingMapper sysFunctionResourceMappingMapper;
    @Resource
    private RequestMappingHandlerMapping handlerMapping;

    @Override
    public Map<Long, Long> getResourceWithPId() {
        return resourceWithPId.asMap();
    }

    @Override
    public boolean containListUrl(String url) {
        return LIST_URL.contains(url);
    }

    @Override
    public boolean containUniqueUrl(String url) {
        return uniqueUrls.contains(url);
    }

    @Override
    public List<SysMenu> getChildrenResources(Long parentId) {
        LambdaQueryWrapper<SysMenu> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(SysMenu::getParentId, parentId);
        return sysMenuMapper.selectList(queryWrapper);
    }

    @PostConstruct
    public void init() {
        initMenuIdMapping();
        initControllerList();
        initDataHandleList();
    }

    private void initDataHandleList() {
        FormUtil.getFromHandles().values().forEach(dataHandle -> {
            FormType formType = dataHandle.getClass().getAnnotation(FormType.class);
            Class<?> clazz = dataHandle.getClass();
            if (!formType.actionClass().equals(void.class)) {
                clazz = formType.actionClass();
            }
            String type = formType.value();
            Streams.of(clazz.getMethods()).forEach(method -> {
                Action annotation = AnnotationUtils.findAnnotation(method, Action.class);
                if (annotation == null) {
                    return;
                }
                String value = annotation.value();
                if (StringUtils.isBlank(value)) {
                    value = method.getName();
                }
                uniqueUrls.add("/form/app/" + type + "/" + value);
                if (annotation.properties() != null) {
                    String finalValue = value;
                    Arrays.stream(annotation.properties()).forEach(property -> {
                        if (Objects.equals(property.name(), AUTH_KEY) && Objects.equals(property.value(), LIST_URL_KEY)) {
                            LIST_URL.add("/form/app/" + type + "/" + finalValue);
                        }
                    });
                }
            });
        });
    }

    private void initControllerList() {
        Map<RequestMappingInfo, HandlerMethod> mapping = handlerMapping.getHandlerMethods();
        mapping.entrySet().stream().filter(entry ->
                        entry.getValue().getBeanType().getName().startsWith("com.hwtx.form") ||
                                entry.getValue().getBeanType().getName().startsWith("io.geekidea.boot"))
                .forEach(entry -> {
                    RequestMappingInfo mappingInfo = entry.getKey();
                    if (mappingInfo.getPathPatternsCondition() == null) {
                        return;
                    }
                    if (entry.getValue().getBeanType().equals(FormController.class)) {
                        return;
                    }
                    mappingInfo.getPathPatternsCondition().getPatterns().forEach(pattern -> {
                        String url = pattern.getPatternString();
                        uniqueUrls.add(url);
                        Method method = entry.getValue().getMethod();
                        Operation operation = AnnotationUtils.findAnnotation(method, Operation.class);
                        if (operation != null && operation.extensions() != null) {
                            Arrays.stream(operation.extensions()).filter(exts -> exts.properties() != null).forEach(exts -> Arrays.stream(exts.properties())
                                    .filter(ext -> Objects.equals(ext.name(), AUTH_KEY) && Objects.equals(ext.value(), LIST_URL_KEY))
                                    .forEach(ext -> LIST_URL.add(url)));
                        }
                    });
                });
    }

    private void initMenuIdMapping() {
        List<SysMenu> sysMenus = sysMenuMapper.getAllValidResourcesWithIdAndPId();
        if (CollectionUtils.isEmpty(sysMenus)) {
            return;
        }
        sysMenus.forEach(sysMenu -> resourceWithPId.put(sysMenu.getId(), sysMenu.getParentId()));
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public boolean addSysMenu(SysMenu sysMenu) {
        if (sysMenu.getParentId() == null) {
            sysMenu.setParentId(SystemConstant.ROOT_MENU_ID);
        }
        if (Objects.equals(sysMenu.getType(), SysMenuType.FUN_PERMISSION.getCode())) {
            sysMenu.setClose(-1);
        }
        if (Objects.equals(sysMenu.getType(), SysMenuType.MENU.getCode())) {
            sysMenu.setComponentPath("amis/index");
        }
        if (Objects.equals(sysMenu.getType(), SysMenuType.DIR.getCode())) {
            sysMenu.setComponentPath("/layouts/BasicLayout");
        }
        boolean ret = save(sysMenu);
        initMenuIdMapping();
        return ret;
    }

    @Override
    public List<SysFunctionResourceMapping> getResourceUrlMapping(Collection<Long> resourceIds) {
        return sysFunctionResourceMappingMapper.selectList(new LambdaQueryWrapper<SysFunctionResourceMapping>().
                in(SysFunctionResourceMapping::getResourceId, resourceIds));

    }

    @Override
    public boolean updateDataPermission(SysMenu sysMenu) {
        return sysMenuMapper.updateById(sysMenu) > 0;
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public boolean updateSysMenu(SysMenu sysMenu) {
        SysMenu exist = getById(sysMenu.getId());
        if (exist == null) {
            throw new BusinessException("系统菜单不存在");
        }
        if (Objects.equals(exist.getType(), SysMenuType.FUN_PERMISSION.getCode())) {
            exist.setClose(-1);
            if (exist.getFId() != null && sysMenu.getCode() != null) {
                String formId = getFormIdByMenuCode(sysMenu.getCode());
                if (StringUtils.isBlank(formId)) {
                    throw new BusinessException("权限格式错误");
                }
                if (!formId.equals(exist.getFId().toString())) {
                    throw new BusinessException("权限格式错误，表单ID与权限标识不一致");
                }
            }
        }
        BeanUtils.copyProperties(sysMenu, exist);
        if (Objects.equals(exist.getType(), SysMenuType.MENU.getCode())) {
            exist.setComponentPath("amis/index");
        }
        if (Objects.equals(exist.getType(), SysMenuType.DIR.getCode())) {
            exist.setComponentPath("/layouts/BasicLayout");
        }
        exist.setUpdateTime(new Date());
        exist.setUpdateId(LoginCache.get().getUsername());
        boolean ret = updateById(exist);
        initMenuIdMapping();
        return ret;
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public boolean deleteSysMenu(Long id) {
        // 获取菜单
        SysMenu sysMenu = getById(id);
        if (sysMenu == null) {
            throw new BusinessException("菜单数据不存在");
        }
        // 所有关联菜单ID集合
        List<Long> deleteMenuIds = new ArrayList<>();
        deleteMenuIds.add(id);
        Integer type = sysMenu.getType();
        SysMenuType sysMenuType = SysMenuType.get(type);
        if (SysMenuType.DIR == sysMenuType) {
            // 如果是目录，则先查询所有的子菜单ID，再根据子菜单ID查询所有的权限菜单ID
            List<Long> menuIds = sysMenuMapper.getChildrenMenuIds(Collections.singletonList(id));
            deleteMenuIds.addAll(menuIds);
            if (CollectionUtils.isNotEmpty(menuIds)) {
                List<Long> permissionMenuIds = sysMenuMapper.getChildrenMenuIds(menuIds);
                deleteMenuIds.addAll(permissionMenuIds);
            }
        } else if (SysMenuType.MENU == sysMenuType) {
            // 如果是菜单，则查询所有的权限子菜单
            List<Long> menuIds = sysMenuMapper.getChildrenMenuIds(Collections.singletonList(id));
            if (CollectionUtils.isNotEmpty(menuIds)) {
                deleteMenuIds.addAll(menuIds);
            }
        }
        // 删除角色菜单关系表中的相关菜单关联
        roleResRepo.deleteSysRoleMenuByMenuIds(deleteMenuIds);

        // 删除所有关联菜单
        boolean ret = removeByIds(deleteMenuIds);
        initMenuIdMapping();
        return ret;
    }

    @Override
    public SysMenu getMenuById(Long id) {
        return sysMenuMapper.selectById(id);
    }

    @Override
    public void deleteResMenuRel(List<Long> menIds) {
        roleResRepo.deleteSysRoleMenuByMenuIds(menIds);
    }

    @Override
    public List<SysMenu> getResources(SysMenu sysMenu) {
        LambdaQueryWrapper<SysMenu> queryWrapper = getMenuCondition(sysMenu);
        return sysMenuMapper.selectList(queryWrapper);
    }

    private LambdaQueryWrapper<SysMenu> getMenuCondition(SysMenu sysMenu) {
        LambdaQueryWrapper<SysMenu> queryWrapper = new LambdaQueryWrapper<>();
        if (StringUtils.isNotBlank(sysMenu.getName())) {
            queryWrapper.eq(SysMenu::getName, sysMenu.getName());
        }
        if (StringUtils.isNotBlank(sysMenu.getRouteUrl())) {
            queryWrapper.eq(SysMenu::getRouteUrl, sysMenu.getRouteUrl());
        }
        if (StringUtils.isNotBlank(sysMenu.getCode())) {
            queryWrapper.eq(SysMenu::getCode, sysMenu.getCode());
        }
        if (sysMenu.getStatus() != null) {
            queryWrapper.eq(SysMenu::getStatus, sysMenu.getStatus());
        }
        if (sysMenu.getFId() != null) {
            queryWrapper.eq(SysMenu::getFId, sysMenu.getFId());
        }
        if (sysMenu.getType() != null) {
            queryWrapper.eq(SysMenu::getType, sysMenu.getType());
        }
        return queryWrapper;
    }

    @Override
    public List<SysMenuTreeVo> getResourceTree(SysMenuQuery query) {
        List<SysMenuTreeVo> list = sysMenuMapper.getSysMenus(query);
        if (CollectionUtils.isEmpty(list)) {
            return Lists.newArrayList();
        }
        // 如果搜索条件有值，则直接返回普通列表
        if (query.getTypes() == null) {
            if (CollectionUtils.isEmpty(query.getResourceIds()) && ObjectValueUtil.isHaveValue(query)) {
                return list.stream().map(vo -> {
                    if (!Objects.equals(vo.getType(), SysMenuType.FUN_PERMISSION.getCode())) {
                        vo.setCode(vo.getRouteUrl());
                    }
                    if (query.getResourceTreeBuildCallback() != null) {
                        if (!query.getResourceTreeBuildCallback().callback(vo)) {
                            return null;
                        }
                    }
                    return vo;
                }).filter(Objects::nonNull).collect(Collectors.toList());
            }
        }
        return recursionSysMenuTreeList(SysMenuType.ROOT.getCode().longValue(), list, query);
    }

    @Override
    public List<PermissionVo> getPermissions(SysMenuQuery query) {
        query.setTypes(List.of(SysMenuType.FUN_PERMISSION.getCode()));
        query.setStatus(true);
        List<SysMenuTreeVo> permissions = sysMenuMapper.getSysMenus(query);
        AtomicInteger atomicInteger = new AtomicInteger(1);
        return permissions.stream().map(vo -> {
            PermissionVo permissionVo = new PermissionVo();
            permissionVo.setNum(atomicInteger.getAndIncrement());
            permissionVo.setCodeInValid(FormUtil.isMenuCodeInValid(vo.getCode()));
            permissionVo.setName(vo.getName());
            permissionVo.setCode(vo.getCode());
            permissionVo.setId(vo.getId());
            if (vo.getFormId() != null) {
                try {
                    FormDef formDef = formService.getFormDef(vo.getFormId());
                    if (formDef != null) {
                        permissionVo.setFormInfo(formDef.getName() + "(" + formDef.getFormId() + ")");
                    }
                } catch (FormNotExistException ignored) {
                }
            }
            if (Objects.equals(vo.getType(), SysMenuType.FUN_PERMISSION.getCode())) {
                permissionVo.setHasListUrl(sysFunctionResourceMappingMapper.selectList(new LambdaQueryWrapper<SysFunctionResourceMapping>()
                                .eq(SysFunctionResourceMapping::getResourceId, vo.getId()))
                        .stream().anyMatch(mapping -> containListUrl(mapping.getUrl())));
            }
            return permissionVo;
        }).collect(Collectors.toList());
    }

    @Override
    public List<ResourceTreeOption> getResourceForRoleTree(SysMenuQuery query) {
        List<ResourceTreeOption> list = sysMenuMapper.getValidResources(query);
        if (CollectionUtils.isEmpty(list)) {
            return Lists.newArrayList();
        }
        return recursionResourceTree(SysMenuType.ROOT.getCode().longValue(), list);
    }

    @Override
    public List<Menu> getResources() {
        // 如果是管理员，则查询所有可用菜单，否则获取当前用户所有可用的菜单
        boolean isAdmin = LoginUtil.isAdmin();
        List<SysMenuTreeVo> list;
        SysMenuQuery query = new SysMenuQuery();
        query.setTypes(List.of(SysMenuType.MENU.getCode(), SysMenuType.DIR.getCode()));
        query.setStatus(true);
        if (isAdmin) {
            list = sysMenuMapper.getSysMenus(query);
        } else {
            List<Long> roleIds = LoginCache.get().getRoleIds();
            list = sysMenuMapper.getMenusByRoleIds(roleIds);
        }
        // 递归返回树形列表
        return convertMenus(SysMenuType.ROOT.getCode().longValue(), list);
    }

    @Override
    public List<Long> getMenuIdsByRoleId(Long roleId, List<Integer> types) {
        return sysMenuMapper.getMenuIdsByRoleId(roleId, types);
    }

    @Override
    public Set<Long> getUserResources(Long userId, List<Integer> types) {
        Set<Long> resIds = sysMenuMapper.getResourceCodeAndIdByUserRole(userId, types).stream().map(SysMenu::getId).collect(Collectors.toSet());
        resIds.addAll(sysMenuMapper.getResourceCodeAndIdByUserOrgRole(userId, types).stream().map(SysMenu::getId).collect(Collectors.toSet()));
        return resIds;
    }

    /**
     * 递归设置树形菜单
     */
    private List<SysMenuTreeVo> recursionSysMenuTreeList(Long parentId, List<SysMenuTreeVo> list, SysMenuQuery query) {
        return list.stream().filter(vo -> vo.getParentId().equals(parentId)).map(item -> {
            item.setChildren(recursionSysMenuTreeList(item.getId(), list, query));
            if (query.getResourceTreeBuildCallback() != null) {
                if (!query.getResourceTreeBuildCallback().callback(item)) {
                    return null;
                }
            }
            return item;
        }).filter(Objects::nonNull).collect(Collectors.toList());
    }

    private List<ResourceTreeOption> recursionResourceTree(Long parentId, List<ResourceTreeOption> options) {
        return options.stream().filter(vo -> vo.getParentId().equals(parentId)).peek(item -> item.setChildren(recursionResourceTree(item.getValue(), options))).collect(Collectors.toList());
    }

    private List<Menu> convertMenus(Long parentId, List<SysMenuTreeVo> list) {
        return list.stream().filter(vo -> vo.getParentId().equals(parentId) &&
                (Objects.equals(vo.getType(), SysMenuType.MENU.getCode()) ||
                        Objects.equals(vo.getType(), SysMenuType.DIR.getCode()))).map(item -> {
            Menu menu = new Menu();
            menu.setPath(item.getRouteUrl());
            if (!Objects.equals(SysMenuType.DIR.getCode(), item.getType())) {
                menu.setComponent(item.getComponentPath());
                menu.setName("resource" + staticIndex++);
            }
            menu.setRedirect(item.getRouteRedirect());
            Menu.Meta meta = new Menu.Meta();
            meta.setTitle(item.getName());
            if (StringUtils.isNotBlank(item.getIcon())) {
                String[] splits = item.getIcon().split(" ");
                if (splits.length == 2) {
                    int index = splits[1].indexOf("fa-");
                    meta.setIcon(splits[0] + ":" + splits[1].substring(index + 3));
                }
            }
            meta.setShowLink(item.getIsShow());
//            meta.setOrderNo(item.getSort());
//            meta.setStatus(item.getStatus() ? 1 : 0);
//            meta.setType(item.getType() - 1);
//            meta.setKeepAlive(BooleanUtils.isTrue(item.getClose()) ? 1 : 0);
            menu.setMeta(meta);
            menu.setChildren(convertMenus(item.getId(), list));
            return menu;
        }).collect(Collectors.toList());
    }
}

